/* * Copyright 2003-2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.attributes; import java.lang.reflect.Field; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; /** * API for accessing attributes. * * <h3>General Notes on Errors</h3> * * All Methods in this class may throw <code>RepositoryError</code> or subclasses thereof. * This Error is thrown if an attribute repository can not be loaded for some Exceptional * reason. * * <h4>Rationale for Errors instead of Exceptions</h4> * * The methods in this class throws <code>Error</code>s instead of <code>Exception</code>s. * This rationale behind this is that: * * <ul> * <li>The programmer should not have to wrap all accesses to * the Attributes API in a try/catch clause. * <li>An Exception being thrown here is caused by missing classes * or other "Error-like" conditions. * </ul> * * <h3>Null References</h3> * * <p>If a parameter to a method may not be null, and a null is passed to the * method, a {@link java.lang.NullPointerException} will be thrown, with the * parameter name in the message. * * <p>Rationale for using this instead of {@link java.lang.IllegalArgumentException} * is that it is more precise - the reference was null. * * <h3>Performance Notes</h3> * <p>The process of loading attributes for a class is a * (relatively) time-consuming process, as it involves some dynamic linking * in the form of inheritable attributes, a lot of reflection and so on. However, * once loaded the attributes are cached, so repeated access to them are fast. * But despite this the process of finding one attribute of a given type * or such operations does involve some iteration of HashSets that <b>runs in linear * time in the number of attributes associated with the program element</b>, and you should * avoid accessing attributes in your innermost loops if you can avoid it. For * example, instead of: * * <pre><code> * Class myClass = ...; * for (int i = 0; i < 10000; i++) { * if (Attributes.hasAttributeType (myClass, MyAttribute.class)) { * doThis(myClass); * } else { * doThat(myClass); * } * } * </code></pre> * * do: * * <pre><code> * Class myClass = ...; * boolean shouldDoThis = Attributes.hasAttributeType (myClass, MyAttribute.class); * for (int i = 0; i < 10000; i++) { * if (shouldDoThis) { * doThis(myClass); * } else { * doThat(myClass); * } * } * </code></pre> * * if the loop should run at maximum speed. * * @since 2.1 */ public class Attributes { /** * A cache of attribute repositories. The map used is a WeakHashMap keyed on the * Class owning the attribute repository. This works because Class objects use * the identity function to compare for equality - i.e. if the two classes * have the same name, and are loaded from the same two ClassLoaders, then * <code>class1 == class2</code>. Also, <code>(class1.equals(class2)) == (class1 == * class2)</code>. This means that a once a Class reference has been garbage-collected, * it can't be re-created. Thus we can treat the cache map as a normal map - the only * entries that will ever disappear are those we can't look up anyway because we * can't ever create the key for them! * * <p>Also, this will keep the cache from growing too big in environments where * classes are loaded and unloaded all the time (i.e. application servers). */ private final static Map classRepositories = new WeakHashMap (); /** * List used to keep track of the initialization list in getCachedRepository. * Since the method is synchronized static, we only need one list. */ private static List initList = new ArrayList (); private synchronized static CachedRepository getCachedRepository (Class clazz) throws RepositoryError, CircularDependencyError { if (initList.contains (clazz)) { List dependencyList = new ArrayList (); dependencyList.addAll (initList); dependencyList.add (clazz); throw new CircularDependencyError (clazz.getName (), dependencyList); } else if (classRepositories.containsKey (clazz)) { CachedRepository cr = (CachedRepository) classRepositories.get (clazz); return cr; } else { // Indicate that we're loading it. CachedRepository cached = null; initList.add (clazz); try { Class attributeRepo = null; AttributeRepositoryClass repo = EmptyAttributeRepositoryClass.INSTANCE; try { attributeRepo = Class.forName (clazz.getName () + "$__attributeRepository", true, clazz.getClassLoader ()); repo = (AttributeRepositoryClass) attributeRepo.newInstance (); } catch (ClassNotFoundException cnfe) { // OK, just means no repo available, so default to empty one. repo = EmptyAttributeRepositoryClass.INSTANCE; } catch (InstantiationException ie) { throw new RepositoryError (ie); } catch (IllegalAccessException iae) { throw new RepositoryError (iae); } cached = new DefaultCachedRepository (clazz, repo); classRepositories.put (clazz, cached); if (repo != null) { Util.validateRepository (clazz, repo); } } finally { initList.remove (initList.size () - 1); } return cached; } } /** * Selects from a collection of attributes one attribute with a given class. * * @param attrs the collection of attribute instances to select from. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * @return the attribute instance, or <code>null</code> of none could be found. * @throws MultipleAttributesError if the collection contains more than one * instance of the specified class. */ private static Object getAttribute (Collection attrs, Class attributeClass) throws MultipleAttributesError { Object candidate = null; Iterator iter = attrs.iterator (); while (iter.hasNext ()) { Object attr = iter.next (); if (attr.getClass () == attributeClass) { if (candidate == null) { candidate = attr; } else { throw new MultipleAttributesError (attributeClass.getName ()); } } } return candidate; } /** * Get one attributes of a given type from a class. * * @param clazz the class. May not be <code>null</code>. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * @return the attribute instance, or <code>null</code> of none could be found. * @throws MultipleAttributesError if the collection contains more than one * instance of the specified class. * * @since 2.1 */ public static Object getAttribute (Class clazz, Class attributeClass) throws RepositoryError, MultipleAttributesError { return getAttribute (getAttributes (clazz), attributeClass); } /** * Get one attributes of a given type from a field. * * @param field the field. May not be <code>null</code>. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * @return the attribute instance, or <code>null</code> of none could be found. * @throws MultipleAttributesError if the collection contains more than one * instance of the specified class. * * @since 2.1 */ public static Object getAttribute (Field field, Class attributeClass) throws RepositoryError, MultipleAttributesError { return getAttribute (getAttributes (field), attributeClass); } /** * Get one attributes of a given type from a constructor. * * @param constructor the constructor. May not be <code>null</code>. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * @return the attribute instance, or <code>null</code> of none could be found. * @throws MultipleAttributesError if the collection contains more than one * instance of the specified class. * * @since 2.1 */ public static Object getAttribute (Constructor constructor, Class attributeClass) throws RepositoryError, MultipleAttributesError { return getAttribute (getAttributes (constructor), attributeClass); } /** * Get one attributes of a given type from a method. * * @param method the method. May not be <code>null</code>. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * @return the attribute instance, or <code>null</code> of none could be found. * @throws MultipleAttributesError if the collection contains more than one * instance of the specified class. * * @since 2.1 */ public static Object getAttribute (Method method, Class attributeClass) throws RepositoryError, MultipleAttributesError { return getAttribute (getAttributes (method), attributeClass); } /** * Get one attributes of a given type from a parameter. * * @param method the method. May not be <code>null</code>. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * @param parameter index of the parameter in the method's parameter list. * @return the attribute instance, or <code>null</code> of none could be found. * @throws MultipleAttributesError if the collection contains more than one * instance of the specified class. * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number * of parameters the method accepts. * * @since 2.1 */ public static Object getParameterAttribute (Method method, int parameter, Class attributeClass) throws RepositoryError, MultipleAttributesError { return getAttribute (getParameterAttributes (method, parameter), attributeClass); } /** * Get one attributes of a given type from a constructor's parameter. * * @param constructor the constructor. May not be <code>null</code>. * @param parameter index of the parameter in the method's parameter list. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * @return the attribute instance, or <code>null</code> of none could be found. * @throws MultipleAttributesError if the collection contains more than one * instance of the specified class. * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number * of parameters the constructor accepts. * * @since 2.1 */ public static Object getParameterAttribute (Constructor constructor, int parameter, Class attributeClass) throws RepositoryError, MultipleAttributesError { return getAttribute (getParameterAttributes (constructor, parameter), attributeClass); } /** * Get one attributes of a given type from a method's return value. * * @param method the method. May not be <code>null</code>. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * @return the attribute instance, or <code>null</code> of none could be found. * @throws MultipleAttributesError if the collection contains more than one * instance of the specified class. * * @since 2.1 */ public static Object getReturnAttribute (Method method, Class attributeClass) throws RepositoryError, MultipleAttributesError { return getAttribute (getReturnAttributes (method), attributeClass); } /** * Gets all attributes for a class. * * @param clazz the class. May not be <code>null</code>. * * @since 2.1 */ public static Collection getAttributes (Class clazz) throws RepositoryError { if (clazz == null) { throw new NullPointerException ("clazz"); } return getCachedRepository (clazz).getAttributes (); } /** * Gets all attributes for a method. * * @param method the method. May not be <code>null</code>. * * @since 2.1 */ public static Collection getAttributes (Method method) throws RepositoryError { if (method == null) { throw new NullPointerException ("method"); } return getCachedRepository (method.getDeclaringClass()).getAttributes (method); } /** * Gets all attributes for a parameter of a method. * * @param method the method. May not be <code>null</code>. * @param parameter the index of the parameter in the method's parameter list. * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number * of parameters the method accepts. * * @since 2.1 */ public static Collection getParameterAttributes (Method method, int parameter) throws RepositoryError { if (method == null) { throw new NullPointerException ("method"); } return getCachedRepository (method.getDeclaringClass()).getParameterAttributes (method, parameter); } /** * Gets all attributes for a parameter of a constructor. * * @param constructor the constructor. May not be <code>null</code>. * @param parameter the index of the parameter in the constructor's parameter list. * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number * of parameters the constructor accepts. * * @since 2.1 */ public static Collection getParameterAttributes (Constructor constructor, int parameter) throws RepositoryError { if (constructor == null) { throw new NullPointerException ("constructor"); } return getCachedRepository (constructor.getDeclaringClass()).getParameterAttributes (constructor, parameter); } /** * Gets all attributes for the return value of a method. * * @param method the method. May not be <code>null</code>. * * @since 2.1 */ public static Collection getReturnAttributes (Method method) throws RepositoryError { if (method == null) { throw new NullPointerException ("method"); } return getCachedRepository (method.getDeclaringClass()).getReturnAttributes (method); } /** * Gets all attributes for a field. * * @param field the field. May not be <code>null</code>. * * @since 2.1 */ public static Collection getAttributes (Field field) throws RepositoryError { if (field == null) { throw new NullPointerException ("field"); } return getCachedRepository (field.getDeclaringClass()).getAttributes (field); } /** * Gets all attributes for a constructor. * * @param constructor the constructor. May not be <code>null</code>. * * @since 2.1 */ public static Collection getAttributes (Constructor constructor) throws RepositoryError { if (constructor == null) { throw new NullPointerException ("constructor"); } return getCachedRepository (constructor.getDeclaringClass()).getAttributes (constructor); } /** * Selects from a collection of attributes only those with a given class. * * @since 2.1 */ private static Collection getAttributes (Collection attrs, Class attributeClass) { HashSet result = new HashSet (); Iterator iter = attrs.iterator (); while (iter.hasNext ()) { Object attr = iter.next (); if (attr.getClass () == attributeClass) { result.add (attr); } } return Collections.unmodifiableCollection (result); } /** * Get all attributes of a given type from a class. For all objects o in the returned * collection, <code>o.getClass() == attributeClass</code>. * * @param clazz the class. May not be <code>null</code>. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * * @since 2.1 */ public static Collection getAttributes (Class clazz, Class attributeClass) throws RepositoryError { return getAttributes (getAttributes (clazz), attributeClass); } /** * Get all attributes of a given type from a field. For all objects o in the returned * collection, <code>o.getClass() == attributeClass</code>. * * @param field the field. May not be <code>null</code>. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * * @since 2.1 */ public static Collection getAttributes (Field field, Class attributeClass) throws RepositoryError { return getAttributes (getAttributes (field), attributeClass); } /** * Get all attributes of a given type from a constructor. For all objects o in the returned * collection, <code>o.getClass() == attributeClass</code>. * * @param constructor the constructor. May not be <code>null</code>. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * * @since 2.1 */ public static Collection getAttributes (Constructor constructor, Class attributeClass) throws RepositoryError { return getAttributes (getAttributes (constructor), attributeClass); } /** * Get all attributes of a given type from a method. For all objects o in the returned * collection, <code>o.getClass() == attributeClass</code>. * * @param method the method. May not be <code>null</code>. * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * * @since 2.1 */ public static Collection getAttributes (Method method, Class attributeClass) throws RepositoryError { return getAttributes (getAttributes (method), attributeClass); } /** * Get all attributes of a given type from a method's parameter. For all objects o in the returned * collection, <code>o.getClass() == attributeClass</code>. * * @param method the method. May not be <code>null</code>. * @param parameter index of the parameter in the method's parameter list * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number * of parameters the method accepts. * * @since 2.1 */ public static Collection getParameterAttributes (Method method, int parameter, Class attributeClass) throws RepositoryError { return getAttributes (getParameterAttributes (method, parameter), attributeClass); } /** * Get all attributes of a given type from a method's parameter. For all objects o in the returned * collection, <code>o.getClass() == attributeClass</code>. * * @param constructor the constructor. May not be <code>null</code>. * @param parameter index of the parameter in the constructor's parameter list * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything. * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number * of parameters the constructor accepts. * * @since 2.1 */ public static Collection getParameterAttributes (Constructor constructor, int parameter, Class attributeClass) throws RepositoryError { return getAttributes (getParameterAttributes (constructor, parameter), attributeClass); } /** * Get all attributes of a given type from a method's return value. For all objects o in the returned * collection, <code>o.getClass() == attributeClass</code>. * * @param method the method * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything. * * @since 2.1 */ public static Collection getReturnAttributes (Method method, Class attributeClass) throws RepositoryError { return getAttributes (getReturnAttributes (method), attributeClass); } /** * Convenience function to test whether a collection of attributes contain * an attribute of a given class. * * @since 2.1 */ private static boolean hasAttributeType (Collection attrs, Class attributeClass) { Iterator iter = attrs.iterator (); while (iter.hasNext ()) { Object attr = iter.next (); if (attr.getClass () == attributeClass) { return true; } } return false; } /** * Tests if a class has an attribute of a given type. That is, is there any attribute * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>? * * @param clazz the class. May not be <code>null</code>. * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything. * * @since 2.1 */ public static boolean hasAttributeType (Class clazz, Class attributeClass) throws RepositoryError { return hasAttributeType (getAttributes (clazz), attributeClass); } /** * Tests if a field has an attribute of a given type. That is, is there any attribute * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>? * * @param field the field. May not be <code>null</code>. * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything. * * @since 2.1 */ public static boolean hasAttributeType (Field field, Class attributeClass) throws RepositoryError { return hasAttributeType (getAttributes (field), attributeClass); } /** * Tests if a constructor has an attribute of a given type. That is, is there any attribute * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>? * * @param constructor the constructor. May not be <code>null</code>. * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything. * * @since 2.1 */ public static boolean hasAttributeType (Constructor constructor, Class attributeClass) throws RepositoryError { return hasAttributeType (getAttributes (constructor), attributeClass); } /** * Tests if a method has an attribute of a given type. That is, is there any attribute * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>? * * @param method the method. May not be <code>null</code>. * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything. * * @since 2.1 */ public static boolean hasAttributeType (Method method, Class attributeClass) throws RepositoryError { return hasAttributeType (getAttributes (method), attributeClass); } /** * Tests if a method's parameter has an attribute of a given type. That is, is there any attribute * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>? * * @param method the method. May not be <code>null</code>. * @param parameter the index of the parameter in the method's parameter list. * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything. * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number * of parameters the method accepts. * * @since 2.1 */ public static boolean hasParameterAttributeType (Method method, int parameter, Class attributeClass) throws RepositoryError { return hasAttributeType (getParameterAttributes (method, parameter), attributeClass); } /** * Tests if a constructor's parameter has an attribute of a given type. That is, is there any attribute * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>? * * @param constructor the constructor. May not be <code>null</code>. * @param parameter the index of the parameter in the constructor's parameter list. * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything. * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number * of parameters the constructor accepts. * * @since 2.1 */ public static boolean hasParameterAttributeType (Constructor constructor, int parameter, Class attributeClass) throws RepositoryError { return hasAttributeType (getParameterAttributes (constructor, parameter), attributeClass); } /** * Tests if a method's return value has an attribute of a given type. That is, is there any attribute * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>? * * @param method the method. May not be <code>null</code>. * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything. * * @since 2.1 */ public static boolean hasReturnAttributeType (Method method, Class attributeClass) throws RepositoryError { return hasAttributeType (getReturnAttributes (method), attributeClass); } /** * Convenience function to test whether a collection of attributes contain * an attribute. * * @since 2.1 */ private static boolean hasAttribute (Collection attrs, Object attribute) throws RepositoryError { return attrs.contains (attribute); } /** * Tests if a class has an attribute. That is, is there any attribute * <code>attr</code> such that <code>attr.equals(attribute)</code>? * * @param clazz the class. May not be <code>null</code>. * @param attribute the attribute to compare to. * * @since 2.1 */ public static boolean hasAttribute (Class clazz, Object attribute) throws RepositoryError { return hasAttribute (getAttributes (clazz), attribute); } /** * Tests if a field has an attribute. That is, is there any attribute * <code>attr</code> such that <code>attr.equals(attribute)</code>? * * @param field the field. May not be <code>null</code>. * @param attribute the attribute to compare to. * * @since 2.1 */ public static boolean hasAttribute (Field field, Object attribute) throws RepositoryError { return hasAttribute (getAttributes (field), attribute); } /** * Tests if a constructor has an attribute. That is, is there any attribute * <code>attr</code> such that <code>attr.equals(attribute)</code>? * * @param constructor the constructor. May not be <code>null</code>. * @param attribute the attribute to compare to. * * @since 2.1 */ public static boolean hasAttribute (Constructor constructor, Object attribute) throws RepositoryError { return hasAttribute (getAttributes (constructor), attribute); } /** * Tests if a method has an attribute. That is, is there any attribute * <code>attr</code> such that <code>attr.equals(attribute)</code>? * * @param method the method. May not be <code>null</code>. * @param attribute the attribute to compare to. * * @since 2.1 */ public static boolean hasAttribute (Method method, Object attribute) throws RepositoryError { return hasAttribute (getAttributes (method), attribute); } /** * Tests if a method's parameter has an attribute. That is, is there any attribute * <code>attr</code> such that <code>attr.equals(attribute)</code>? * * @param method the method. May not be <code>null</code>. * @param parameter the index of the parameter in the method's parameter list. * @param attribute the attribute to compare to. * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number * of parameters the method accepts. * * @since 2.1 */ public static boolean hasParameterAttribute (Method method, int parameter, Object attribute) throws RepositoryError { return hasAttribute (getParameterAttributes (method, parameter), attribute); } /** * Tests if a constructor's parameter has an attribute. That is, is there any attribute * <code>attr</code> such that <code>attr.equals(attribute)</code>? * * @param constructor the constructor. May not be <code>null</code>. * @param parameter the index of the parameter in the constructor's parameter list. * @param attribute the attribute to compare to. * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number * of parameters the constructor accepts. * * @since 2.1 */ public static boolean hasParameterAttribute (Constructor constructor, int parameter, Object attribute) throws RepositoryError { return hasAttribute (getParameterAttributes (constructor, parameter), attribute); } /** * Tests if a method's return value has an attribute. That is, is there any attribute * <code>attr</code> such that <code>attr.equals(attribute)</code>? * * @param method the method. May not be <code>null</code>. * @param attribute the attribute to compare to. * * @since 2.1 */ public static boolean hasReturnAttribute (Method method, Object attribute) throws RepositoryError { return hasAttribute (getReturnAttributes (method), attribute); } /** * Set attributes for a given class. The class must not have attributes set for it already * (i.e. you can't redefine the attributes of a class at runtime). This because it * really messes up the concept of inheritable attributes, and because the attribute set * associated with a class is immutable, like the set of methods and fields. * * @param repo The repository. The repository will be sealed before any attributes are * set. This to guarantee that the repository remains constant * during setting. * @throws IllegalStateException if the class whose attributes are defined already have * attributes defined for it (even if it has no attributes). * @since 2.1 */ public static synchronized void setAttributes (RuntimeAttributeRepository repo) throws IllegalStateException { if (repo == null) { throw new NullPointerException ("repo"); } repo.seal (); Class clazz = repo.getDefinedClass (); if (classRepositories.get (clazz) != null) { throw new IllegalStateException (clazz.getName ()); } Util.validateRepository (clazz, repo); DefaultCachedRepository cached = new DefaultCachedRepository (clazz, repo); classRepositories.put (clazz, cached); } }